package lang

import (
	"fmt"
	"bytes"
	"strconv"
)

type ObjType int

const (
	ObjNil  ObjType = iota // special nil (nothing) type
	ObjBool                // boolean (true, false)
	ObjNum                 // number
	ObjStr                 // string
	ObjList                // array-like type
	ObjFunc                // function
)

var objTypeNames = map[ObjType]string{
	ObjNil:  "nil",
	ObjBool: "bool",
	ObjNum:  "number",
	ObjStr:  "string",
	ObjList: "list",
	ObjFunc: "func",
}

func (o ObjType) Type() ObjType {
	return o
}

func (o ObjType) String() string {
	s := objTypeNames[o]
	if s == "" {
		return fmt.Sprintf("obj%d", int(o))
	}
	return s
}

//TObj is any language object
type TObj interface {
	String() string
	Type() ObjType
	Copy() TObj       // return a copy
	DeepCopy() TObj   // deep copy
	Equals(TObj) bool // determine equality
	As(ObjType) TObj  // return as given type
}

type TNil struct {
	ObjType
}

func (n *TNil) Copy() TObj {
	return n //XXX should this be an error
}

func (n *TNil) DeepCopy() TObj {
	return n.Copy()
}

func (n *TNil) Equals(o TObj) bool {
	return o.Type() == ObjNil
}

func (n *TNil) As(o ObjType) TObj {
	//TODO finish me
	switch o {
	case ObjNil:
		return n
	case ObjBool:
		return FALSE //TODO define me
	case ObjStr:
		return intern("")
	case ObjNum:
		return newTNum(0)
	default:
		return n
	}
	panic("cannot get here ever")
}

func newNil() *TNil {
	return &TNil{ObjType: ObjNil}
}

type TBool struct {
	ObjType
	Val bool
}

func (b *TBool) String() string {
	return fmt.Sprintf("%t", b.Val)
}

func (b *TBool) Copy() TObj {
	return b
}

func (b *TBool) DeepCopy() TObj {
	return b.Copy()
}

func (b *TBool) Equals(o TObj) bool {
	bval := o.As(ObjBool)
	return bval.(*TBool).Val == b.Val
}

func (b *TBool) As(o ObjType) TObj {
	switch o {
	case ObjStr:
		if b.Val {
			return intern("true")
		}
		return intern("false")
	case ObjNum:
		if b.Val {
			return newTNum(1)
		}
		return newTNum(0)
	}
	return b
}

func newTBool(val bool) *TBool {
	return &TBool{ObjType: ObjBool, Val: val}
}

var FALSE = newTBool(false)
var TRUE = newTBool(true)

type TNum struct {
	ObjType
	I     int64
	U     uint64
	F     float64
	Int   bool
	Uint  bool
	Float bool
}

func (n *TNum) String() string {
	switch {
	case n.Float:
		return strconv.Ftoa64(n.F, 'g', -1)
	case n.Uint:
		return strconv.Uitoa64(n.U)
	case n.Int:
		return strconv.Itoa64(n.I)
	}
	panic("bad number")
}

func (n *TNum) Copy() TObj {
	return &TNum{ObjType: ObjNum, I: n.I, U: n.U, F: n.F, Int: n.Int, Uint: n.Uint, Float: n.Float}
}

func (n *TNum) DeepCopy() TObj {
	return n.Copy()
}

func (n *TNum) Equals(o TObj) bool {
	nval := o.As(ObjNum).(*TNum)
	switch {
	case nval.Float:
		return nval.F == n.F
	case nval.Uint:
		return nval.U == n.U
	case nval.Int:
		return nval.I == n.I
	}
	panic("bad number ==")
}

func (n *TNum) As(o ObjType) TObj {
	return n
}

func newTNum(num interface{}) *TNum {
	switch n := num.(type) {
	case *NumberNode:
		nn := num.(*NumberNode)
		return &TNum{
			ObjType: ObjNum,
			Float:   nn.IsFloat,
			Uint:    nn.IsUint,
			Int:     nn.IsInt,
			F:       nn.Float64,
			U:       nn.Uint64,
			I:       nn.Int64,
		}
	case int, int8, int16, int32, int64:
		iv := int64(n.(int64))
		return &TNum{
			ObjType: ObjNum,
			Float:   false,
			Uint:    int64(uint64(iv)) == iv,
			Int:     true,
			F:       float64(iv),
			U:       uint64(iv),
			I:       iv,
		}
		//fallthrough
	case uint, uint8, uint16, uint32, uint64:
		uiv := uint64(n.(uint64))
		return &TNum{
			ObjType: ObjNum,
			Float:   false,
			Uint:    true,
			Int:     true,
			F:       float64(uiv),
			U:       uiv,
			I:       int64(uiv),
		}
	case float32, float64:
		fv := float64(n.(float64))
		return &TNum{
			ObjType: ObjNum,
			Float:   true,
			Uint:    true,
			Int:     true,
			F:       fv,
			U:       uint64(fv),
			I:       int64(fv),
		}
	}
	panic("bad number creation")
}

type TStr struct {
	ObjType
	Val []byte
}

func (s *TStr) String() string {
	return string(s.Val)
}

func (s *TStr) Copy() TObj {
	return newTStr(dup(s.Val))
}

func (s *TStr) DeepCopy() TObj {
	return s.Copy()
}

func (s *TStr) Equals(t TObj) bool {
	sval := t.As(ObjStr)
	return bytes.Equal(s.Val, sval.(*TStr).Val)
}

func (s *TStr) As(typ ObjType) TObj {
	switch typ {
	case ObjStr:
		return s
	}
	return nil
}

func newTStr(val []byte) *TStr {
	return &TStr{ObjType: ObjStr, Val: val}
}

//an interned string
type Interned struct {
	ObjType
	TStr
}

func (t *Interned) String() string {
	return t.TStr.String()
}
//XXX is this ok? copying an interned string just gives the same thing back to you
func (t *Interned) Copy() TObj {
	return t
}

//TODO PUT THIS IN THE INTERN POOL
func newInterned(val *TStr) *Interned {
	return &Interned{ObjType: ObjStr, TStr: *val}
}

type TList struct {
	ObjType
	Items []TObj
}

func (l *TList) String() string {
	return "TList" //TODO
}

func (l *TList) Copy() TObj {
	nitems := make([]TObj, len(l.Items))
	copy(nitems, l.Items)
	return &TList{ObjType: ObjList, Items: nitems}
}

func (l *TList) DeepCopy() TObj {
	nitems := make([]TObj, len(l.Items))
	for i, v := range l.Items {
		nitems[i] = v.DeepCopy()
	}
	return &TList{ObjType: ObjList, Items: nitems}
}

func (l *TList) Equals(o TObj) bool {
	if o.Type() != ObjList {
		return false
	}
	ol := o.(*TList)
	for i, v := range l.Items {
		if !ol.Items[i].Equals(v) {
			return false
		}
	}
	return true
}

func (l *TList) As(o ObjType) TObj {
	return l
}

func newTList() *TList {
	return &TList{ObjType: ObjList, Items: make([]TObj, 0, 1)}
}
